@@ -7,12 +7,15 @@ import android.support.v7.widget.GridLayoutManager; |
||
7 | 7 |
import android.support.v7.widget.LinearLayoutManager; |
8 | 8 |
import android.support.v7.widget.RecyclerView; |
9 | 9 |
import android.view.View; |
10 |
+import android.widget.Button; |
|
10 | 11 |
import android.widget.ImageView; |
12 |
+import android.widget.RelativeLayout; |
|
11 | 13 |
import android.widget.TextView; |
12 | 14 |
import android.widget.Toast; |
13 | 15 |
|
14 | 16 |
import com.android.common.utils.LogHelper; |
15 | 17 |
import com.android.common.utils.NetworkUtil; |
18 |
+import com.android.views.loadingdrawable.LoadingView; |
|
16 | 19 |
import com.umeng.analytics.MobclickAgent; |
17 | 20 |
|
18 | 21 |
import java.util.ArrayList; |
@@ -36,6 +39,10 @@ public class MainActivity extends BaseActivity implements MainContract.View { |
||
36 | 39 |
@BindView(R.id.iv_add_session) ImageView addSessionBtn; |
37 | 40 |
@BindView(R.id.container_view) View containerView; |
38 | 41 |
@BindView(R.id.recycler_view_sessions) RecyclerView sessionsRecyclerView; |
42 |
+ @BindView(R.id.sync_time_view) RelativeLayout syncTimeView; |
|
43 |
+ @BindView(R.id.loading_gear_view) LoadingView loadingView; |
|
44 |
+ @BindView(R.id.tv_sync_time_status) TextView syncStatusTextView; |
|
45 |
+ @BindView(R.id.btn_retry_sync) Button retrySyncBtn; |
|
39 | 46 |
private SessionRecyclerAdapter adapter; |
40 | 47 |
private MainContract.Presenter presenter; |
41 | 48 |
private long exitTime; |
@@ -77,6 +84,12 @@ public class MainActivity extends BaseActivity implements MainContract.View { |
||
77 | 84 |
} |
78 | 85 |
} |
79 | 86 |
|
87 |
+ @OnClick(R.id.btn_retry_sync) |
|
88 |
+ void retrySync(){ |
|
89 |
+ showTimeSyncView(); |
|
90 |
+ presenter.resyncTime(); |
|
91 |
+ } |
|
92 |
+ |
|
80 | 93 |
@OnClick(R.id.layout_brief) |
81 | 94 |
void jumpToBriefs(){ |
82 | 95 |
MobclickAgent.onEvent(this, UmengEvent.home_brief_btn_click); |
@@ -120,8 +133,9 @@ public class MainActivity extends BaseActivity implements MainContract.View { |
||
120 | 133 |
|
121 | 134 |
@Override |
122 | 135 |
public void showSessionViews() { |
123 |
- noDataLayout.setVisibility(android.view.View.GONE); |
|
124 |
- sessionsRecyclerView.setVisibility(android.view.View.VISIBLE); |
|
136 |
+ noDataLayout.setVisibility(View.GONE); |
|
137 |
+ syncTimeView.setVisibility(View.GONE); |
|
138 |
+ sessionsRecyclerView.setVisibility(View.VISIBLE); |
|
125 | 139 |
} |
126 | 140 |
|
127 | 141 |
@Override |
@@ -140,6 +154,23 @@ public class MainActivity extends BaseActivity implements MainContract.View { |
||
140 | 154 |
addSessionBtn.setEnabled(isEnabled); |
141 | 155 |
} |
142 | 156 |
|
157 |
+ @Override |
|
158 |
+ public void showTimeSyncView() { |
|
159 |
+ sessionsRecyclerView.setVisibility(View.GONE); |
|
160 |
+ syncTimeView.setVisibility(View.VISIBLE); |
|
161 |
+ loadingView.setVisibility(View.VISIBLE); |
|
162 |
+ syncStatusTextView.setText(R.string.time_syncing); |
|
163 |
+ } |
|
164 |
+ |
|
165 |
+ @Override |
|
166 |
+ public void showRetrySyncView(int strId) { |
|
167 |
+ retrySyncBtn.setVisibility(View.VISIBLE); |
|
168 |
+ loadingView.setVisibility(View.INVISIBLE); |
|
169 |
+ if(strId!=0){ |
|
170 |
+ syncStatusTextView.setText(strId); |
|
171 |
+ } |
|
172 |
+ |
|
173 |
+ } |
|
143 | 174 |
|
144 | 175 |
private void jumpToSelectedSession(SessionBean sessionBean) { |
145 | 176 |
Intent intent = new Intent(this, SessionActivity.class); |
@@ -18,11 +18,13 @@ public class MainContract { |
||
18 | 18 |
void updateSessionUploadViewAt(int position); |
19 | 19 |
void refreshSessionViews(ArrayList<SessionBean> sessionList); |
20 | 20 |
void setNewSessionBtnEnabled(boolean isEnabled); |
21 |
+ void showTimeSyncView(); |
|
22 |
+ void showRetrySyncView(int strId); |
|
21 | 23 |
} |
22 | 24 |
|
23 | 25 |
interface Presenter extends BasePresenter{ |
24 | 26 |
SessionBean createNewSession(); |
25 |
- |
|
27 |
+ void resyncTime(); |
|
26 | 28 |
} |
27 | 29 |
|
28 | 30 |
} |
@@ -103,6 +103,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL |
||
103 | 103 |
view.showBoxDisconnectedView(); |
104 | 104 |
} |
105 | 105 |
view.setNewSessionBtnEnabled(false); |
106 |
+ view.showTimeSyncView(); |
|
106 | 107 |
if(sessionIds==null|| sessionIds.size()<20){ |
107 | 108 |
fetchSessionIdsInteractor.startJob(); |
108 | 109 |
} |
@@ -136,6 +137,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL |
||
136 | 137 |
if(sessionIds==null|sessionIds.size()<20){ |
137 | 138 |
return; |
138 | 139 |
} |
140 |
+ |
|
139 | 141 |
view.setNewSessionBtnEnabled(true); |
140 | 142 |
if(sessionList.size()==0){ |
141 | 143 |
view.showEmptyView(); |
@@ -169,6 +171,14 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL |
||
169 | 171 |
return sessionBean; |
170 | 172 |
} |
171 | 173 |
|
174 |
+ @Override |
|
175 |
+ public void resyncTime() { |
|
176 |
+ if(syncTimeInteractor!=null){ |
|
177 |
+ syncTimeInteractor.cancelJob(); |
|
178 |
+ syncTimeInteractor.startJob(); |
|
179 |
+ } |
|
180 |
+ } |
|
181 |
+ |
|
172 | 182 |
private long getSessionDateInLongFormat(){ |
173 | 183 |
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd"); |
174 | 184 |
String dateStr = format.format(new Date()); |
@@ -274,7 +284,7 @@ class MainPresenter implements MainContract.Presenter,BaseInteractor.InteractorL |
||
274 | 284 |
onDataReady(); |
275 | 285 |
}else{ |
276 | 286 |
if(strId!=0){ |
277 |
- view.showSnackBar(strId); |
|
287 |
+ view.showRetrySyncView(strId); |
|
278 | 288 |
} |
279 | 289 |
} |
280 | 290 |
} |
@@ -0,0 +1,12 @@ |
||
1 |
+<?xml version="1.0" encoding="utf-8"?> |
|
2 |
+<shape xmlns:android="http://schemas.android.com/apk/res/android" |
|
3 |
+ android:shape="rectangle" > |
|
4 |
+ |
|
5 |
+ <solid android:color="@color/transparent" /> |
|
6 |
+ |
|
7 |
+ <corners |
|
8 |
+ android:radius="6dp"/> |
|
9 |
+ |
|
10 |
+ <stroke android:width="1dp" android:color="@color/white"/> |
|
11 |
+ |
|
12 |
+</shape> |
@@ -1,5 +1,6 @@ |
||
1 | 1 |
<?xml version="1.0" encoding="utf-8"?> |
2 | 2 |
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" |
3 |
+ xmlns:app="http://schemas.android.com/apk/res-auto" |
|
3 | 4 |
android:id="@+id/container_view" |
4 | 5 |
android:layout_width="match_parent" |
5 | 6 |
android:layout_height="match_parent" |
@@ -54,6 +55,46 @@ |
||
54 | 55 |
android:layout_centerInParent="true" |
55 | 56 |
android:src="@drawable/no_photo_tip" /> |
56 | 57 |
|
58 |
+ <RelativeLayout |
|
59 |
+ android:id="@+id/sync_time_view" |
|
60 |
+ android:layout_width="match_parent" |
|
61 |
+ android:layout_height="match_parent" |
|
62 |
+ android:background="@color/half_transparent"> |
|
63 |
+ |
|
64 |
+ <com.android.views.loadingdrawable.LoadingView |
|
65 |
+ android:id="@+id/loading_gear_view" |
|
66 |
+ android:layout_width="180dp" |
|
67 |
+ android:layout_height="180dp" |
|
68 |
+ android:layout_centerInParent="true" |
|
69 |
+ app:loading_renderer="GearLoadingRenderer"/> |
|
70 |
+ |
|
71 |
+ <TextView |
|
72 |
+ android:id="@+id/tv_sync_time_status" |
|
73 |
+ android:layout_width="wrap_content" |
|
74 |
+ android:layout_height="wrap_content" |
|
75 |
+ android:textSize="16sp" |
|
76 |
+ android:text="@string/time_syncing" |
|
77 |
+ android:layout_below="@id/loading_gear_view" |
|
78 |
+ android:layout_marginTop="10dp" |
|
79 |
+ android:layout_centerHorizontal="true" |
|
80 |
+ android:textColor="@color/white"/> |
|
81 |
+ |
|
82 |
+ <Button |
|
83 |
+ android:id="@+id/btn_retry_sync" |
|
84 |
+ android:layout_width="140dp" |
|
85 |
+ android:layout_height="40dp" |
|
86 |
+ android:layout_centerHorizontal="true" |
|
87 |
+ android:layout_below="@id/tv_sync_time_status" |
|
88 |
+ android:layout_marginTop="10dp" |
|
89 |
+ android:background="@drawable/retry_sync_btn_rounded_rect_bg" |
|
90 |
+ android:gravity="center" |
|
91 |
+ android:text="@string/time_sync_retry" |
|
92 |
+ android:textColor="@color/white" |
|
93 |
+ android:visibility="gone" |
|
94 |
+ android:textSize="18sp" /> |
|
95 |
+ </RelativeLayout> |
|
96 |
+ |
|
97 |
+ |
|
57 | 98 |
<android.support.v7.widget.RecyclerView |
58 | 99 |
android:id="@+id/recycler_view_sessions" |
59 | 100 |
android:layout_width="match_parent" |
@@ -174,7 +174,11 @@ |
||
174 | 174 |
|
175 | 175 |
<string name="upload_settings">上传管理</string> |
176 | 176 |
|
177 |
- <string name="sync_time_server_error">时间同步出错,请检查网络连接后退出重试</string> |
|
178 |
- <string name="sync_time_box_error">时间同步出错,请检查盒子是否打开后退出重试</string> |
|
177 |
+ <string name="sync_time_server_error">时间同步出错,请检查网络连接</string> |
|
178 |
+ <string name="sync_time_box_error">时间同步出错,请检查盒子是否打开</string> |
|
179 |
+ |
|
180 |
+ <string name="time_syncing">正在同步时间...</string> |
|
181 |
+ |
|
182 |
+ <string name="time_sync_retry">重新同步</string> |
|
179 | 183 |
|
180 | 184 |
</resources> |
@@ -0,0 +1,11 @@ |
||
1 |
+package com.android.views.loadingdrawable; |
|
2 |
+ |
|
3 |
+import android.content.Context; |
|
4 |
+ |
|
5 |
+public class DensityUtil { |
|
6 |
+ |
|
7 |
+ public static float dip2px(Context context, float dpValue) { |
|
8 |
+ float scale = context.getResources().getDisplayMetrics().density; |
|
9 |
+ return dpValue * scale; |
|
10 |
+ } |
|
11 |
+} |
@@ -0,0 +1,78 @@ |
||
1 |
+package com.android.views.loadingdrawable; |
|
2 |
+ |
|
3 |
+import android.content.Context; |
|
4 |
+import android.content.res.TypedArray; |
|
5 |
+import android.util.AttributeSet; |
|
6 |
+import android.view.View; |
|
7 |
+import android.widget.ImageView; |
|
8 |
+ |
|
9 |
+import com.android.views.R; |
|
10 |
+import com.android.views.loadingdrawable.render.LoadingDrawable; |
|
11 |
+import com.android.views.loadingdrawable.render.LoadingRenderer; |
|
12 |
+import com.android.views.loadingdrawable.render.LoadingRendererFactory; |
|
13 |
+ |
|
14 |
+public class LoadingView extends ImageView { |
|
15 |
+ private LoadingDrawable mLoadingDrawable; |
|
16 |
+ |
|
17 |
+ public LoadingView(Context context) { |
|
18 |
+ super(context); |
|
19 |
+ } |
|
20 |
+ |
|
21 |
+ public LoadingView(Context context, AttributeSet attrs) { |
|
22 |
+ super(context, attrs); |
|
23 |
+ initAttrs(context, attrs); |
|
24 |
+ } |
|
25 |
+ |
|
26 |
+ private void initAttrs(Context context, AttributeSet attrs) { |
|
27 |
+ try { |
|
28 |
+ TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); |
|
29 |
+ int loadingRendererId = ta.getInt(R.styleable.LoadingView_loading_renderer, 0); |
|
30 |
+ LoadingRenderer loadingRenderer = LoadingRendererFactory.createLoadingRenderer(context, loadingRendererId); |
|
31 |
+ setLoadingRenderer(loadingRenderer); |
|
32 |
+ ta.recycle(); |
|
33 |
+ } catch (Exception e) { |
|
34 |
+ e.printStackTrace(); |
|
35 |
+ } |
|
36 |
+ } |
|
37 |
+ |
|
38 |
+ public void setLoadingRenderer(LoadingRenderer loadingRenderer) { |
|
39 |
+ mLoadingDrawable = new LoadingDrawable(loadingRenderer); |
|
40 |
+ setImageDrawable(mLoadingDrawable); |
|
41 |
+ } |
|
42 |
+ |
|
43 |
+ @Override |
|
44 |
+ protected void onAttachedToWindow() { |
|
45 |
+ super.onAttachedToWindow(); |
|
46 |
+ startAnimation(); |
|
47 |
+ } |
|
48 |
+ |
|
49 |
+ @Override |
|
50 |
+ protected void onDetachedFromWindow() { |
|
51 |
+ super.onDetachedFromWindow(); |
|
52 |
+ stopAnimation(); |
|
53 |
+ } |
|
54 |
+ |
|
55 |
+ @Override |
|
56 |
+ protected void onVisibilityChanged(View changedView, int visibility) { |
|
57 |
+ super.onVisibilityChanged(changedView, visibility); |
|
58 |
+ |
|
59 |
+ final boolean visible = visibility == VISIBLE && getVisibility() == VISIBLE; |
|
60 |
+ if (visible) { |
|
61 |
+ startAnimation(); |
|
62 |
+ } else { |
|
63 |
+ stopAnimation(); |
|
64 |
+ } |
|
65 |
+ } |
|
66 |
+ |
|
67 |
+ private void startAnimation() { |
|
68 |
+ if (mLoadingDrawable != null) { |
|
69 |
+ mLoadingDrawable.start(); |
|
70 |
+ } |
|
71 |
+ } |
|
72 |
+ |
|
73 |
+ private void stopAnimation() { |
|
74 |
+ if (mLoadingDrawable != null) { |
|
75 |
+ mLoadingDrawable.stop(); |
|
76 |
+ } |
|
77 |
+ } |
|
78 |
+} |
@@ -0,0 +1,87 @@ |
||
1 |
+package com.android.views.loadingdrawable.render; |
|
2 |
+ |
|
3 |
+import android.graphics.Canvas; |
|
4 |
+import android.graphics.ColorFilter; |
|
5 |
+import android.graphics.PixelFormat; |
|
6 |
+import android.graphics.Rect; |
|
7 |
+import android.graphics.drawable.Animatable; |
|
8 |
+import android.graphics.drawable.Drawable; |
|
9 |
+ |
|
10 |
+public class LoadingDrawable extends Drawable implements Animatable { |
|
11 |
+ private final LoadingRenderer mLoadingRender; |
|
12 |
+ |
|
13 |
+ private final Callback mCallback = new Callback() { |
|
14 |
+ @Override |
|
15 |
+ public void invalidateDrawable(Drawable d) { |
|
16 |
+ invalidateSelf(); |
|
17 |
+ } |
|
18 |
+ |
|
19 |
+ @Override |
|
20 |
+ public void scheduleDrawable(Drawable d, Runnable what, long when) { |
|
21 |
+ scheduleSelf(what, when); |
|
22 |
+ } |
|
23 |
+ |
|
24 |
+ @Override |
|
25 |
+ public void unscheduleDrawable(Drawable d, Runnable what) { |
|
26 |
+ unscheduleSelf(what); |
|
27 |
+ } |
|
28 |
+ }; |
|
29 |
+ |
|
30 |
+ public LoadingDrawable(LoadingRenderer loadingRender) { |
|
31 |
+ this.mLoadingRender = loadingRender; |
|
32 |
+ this.mLoadingRender.setCallback(mCallback); |
|
33 |
+ } |
|
34 |
+ |
|
35 |
+ @Override |
|
36 |
+ protected void onBoundsChange(Rect bounds) { |
|
37 |
+ super.onBoundsChange(bounds); |
|
38 |
+ this.mLoadingRender.setBounds(bounds); |
|
39 |
+ } |
|
40 |
+ |
|
41 |
+ @Override |
|
42 |
+ public void draw(Canvas canvas) { |
|
43 |
+ if (!getBounds().isEmpty()) { |
|
44 |
+ this.mLoadingRender.draw(canvas); |
|
45 |
+ } |
|
46 |
+ } |
|
47 |
+ |
|
48 |
+ @Override |
|
49 |
+ public void setAlpha(int alpha) { |
|
50 |
+ this.mLoadingRender.setAlpha(alpha); |
|
51 |
+ } |
|
52 |
+ |
|
53 |
+ @Override |
|
54 |
+ public void setColorFilter(ColorFilter cf) { |
|
55 |
+ this.mLoadingRender.setColorFilter(cf); |
|
56 |
+ } |
|
57 |
+ |
|
58 |
+ @Override |
|
59 |
+ public int getOpacity() { |
|
60 |
+ return PixelFormat.TRANSLUCENT; |
|
61 |
+ } |
|
62 |
+ |
|
63 |
+ @Override |
|
64 |
+ public void start() { |
|
65 |
+ this.mLoadingRender.start(); |
|
66 |
+ } |
|
67 |
+ |
|
68 |
+ @Override |
|
69 |
+ public void stop() { |
|
70 |
+ this.mLoadingRender.stop(); |
|
71 |
+ } |
|
72 |
+ |
|
73 |
+ @Override |
|
74 |
+ public boolean isRunning() { |
|
75 |
+ return this.mLoadingRender.isRunning(); |
|
76 |
+ } |
|
77 |
+ |
|
78 |
+ @Override |
|
79 |
+ public int getIntrinsicHeight() { |
|
80 |
+ return (int) this.mLoadingRender.mHeight; |
|
81 |
+ } |
|
82 |
+ |
|
83 |
+ @Override |
|
84 |
+ public int getIntrinsicWidth() { |
|
85 |
+ return (int) this.mLoadingRender.mWidth; |
|
86 |
+ } |
|
87 |
+} |
@@ -0,0 +1,120 @@ |
||
1 |
+package com.android.views.loadingdrawable.render; |
|
2 |
+ |
|
3 |
+import android.animation.Animator; |
|
4 |
+import android.animation.ValueAnimator; |
|
5 |
+import android.content.Context; |
|
6 |
+import android.graphics.Canvas; |
|
7 |
+import android.graphics.ColorFilter; |
|
8 |
+import android.graphics.Rect; |
|
9 |
+import android.graphics.drawable.Drawable; |
|
10 |
+import android.view.animation.Animation; |
|
11 |
+import android.view.animation.LinearInterpolator; |
|
12 |
+ |
|
13 |
+import com.android.views.loadingdrawable.DensityUtil; |
|
14 |
+ |
|
15 |
+public abstract class LoadingRenderer { |
|
16 |
+ private static final long ANIMATION_DURATION = 1333; |
|
17 |
+ private static final float DEFAULT_SIZE = 56.0f; |
|
18 |
+ |
|
19 |
+ private final ValueAnimator.AnimatorUpdateListener mAnimatorUpdateListener |
|
20 |
+ = new ValueAnimator.AnimatorUpdateListener() { |
|
21 |
+ @Override |
|
22 |
+ public void onAnimationUpdate(ValueAnimator animation) { |
|
23 |
+ computeRender((float) animation.getAnimatedValue()); |
|
24 |
+ invalidateSelf(); |
|
25 |
+ } |
|
26 |
+ }; |
|
27 |
+ |
|
28 |
+ /** |
|
29 |
+ * Whenever {@link LoadingDrawable} boundary changes mBounds will be updated. |
|
30 |
+ * More details you can see {@link LoadingDrawable#onBoundsChange(Rect)} |
|
31 |
+ */ |
|
32 |
+ protected final Rect mBounds = new Rect(); |
|
33 |
+ |
|
34 |
+ private Drawable.Callback mCallback; |
|
35 |
+ private ValueAnimator mRenderAnimator; |
|
36 |
+ |
|
37 |
+ protected long mDuration; |
|
38 |
+ |
|
39 |
+ protected float mWidth; |
|
40 |
+ protected float mHeight; |
|
41 |
+ |
|
42 |
+ public LoadingRenderer(Context context) { |
|
43 |
+ initParams(context); |
|
44 |
+ setupAnimators(); |
|
45 |
+ } |
|
46 |
+ |
|
47 |
+ @Deprecated |
|
48 |
+ protected void draw(Canvas canvas, Rect bounds) { |
|
49 |
+ } |
|
50 |
+ |
|
51 |
+ protected void draw(Canvas canvas) { |
|
52 |
+ draw(canvas, mBounds); |
|
53 |
+ } |
|
54 |
+ |
|
55 |
+ protected abstract void computeRender(float renderProgress); |
|
56 |
+ |
|
57 |
+ protected abstract void setAlpha(int alpha); |
|
58 |
+ |
|
59 |
+ protected abstract void setColorFilter(ColorFilter cf); |
|
60 |
+ |
|
61 |
+ protected abstract void reset(); |
|
62 |
+ |
|
63 |
+ protected void addRenderListener(Animator.AnimatorListener animatorListener) { |
|
64 |
+ mRenderAnimator.addListener(animatorListener); |
|
65 |
+ } |
|
66 |
+ |
|
67 |
+ void start() { |
|
68 |
+ reset(); |
|
69 |
+ mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); |
|
70 |
+ |
|
71 |
+ mRenderAnimator.setRepeatCount(ValueAnimator.INFINITE); |
|
72 |
+ mRenderAnimator.setDuration(mDuration); |
|
73 |
+ mRenderAnimator.start(); |
|
74 |
+ } |
|
75 |
+ |
|
76 |
+ void stop() { |
|
77 |
+ // if I just call mRenderAnimator.end(), |
|
78 |
+ // it will always call the method onAnimationUpdate(ValueAnimator animation) |
|
79 |
+ // why ? if you know why please send email to me (dinus_developer@163.com) |
|
80 |
+ mRenderAnimator.removeUpdateListener(mAnimatorUpdateListener); |
|
81 |
+ |
|
82 |
+ mRenderAnimator.setRepeatCount(0); |
|
83 |
+ mRenderAnimator.setDuration(0); |
|
84 |
+ mRenderAnimator.end(); |
|
85 |
+ } |
|
86 |
+ |
|
87 |
+ boolean isRunning() { |
|
88 |
+ return mRenderAnimator.isRunning(); |
|
89 |
+ } |
|
90 |
+ |
|
91 |
+ void setCallback(Drawable.Callback callback) { |
|
92 |
+ this.mCallback = callback; |
|
93 |
+ } |
|
94 |
+ |
|
95 |
+ void setBounds(Rect bounds) { |
|
96 |
+ mBounds.set(bounds); |
|
97 |
+ } |
|
98 |
+ |
|
99 |
+ private void initParams(Context context) { |
|
100 |
+ mWidth = DensityUtil.dip2px(context, DEFAULT_SIZE); |
|
101 |
+ mHeight = DensityUtil.dip2px(context, DEFAULT_SIZE); |
|
102 |
+ |
|
103 |
+ mDuration = ANIMATION_DURATION; |
|
104 |
+ } |
|
105 |
+ |
|
106 |
+ private void setupAnimators() { |
|
107 |
+ mRenderAnimator = ValueAnimator.ofFloat(0.0f, 1.0f); |
|
108 |
+ mRenderAnimator.setRepeatCount(Animation.INFINITE); |
|
109 |
+ mRenderAnimator.setRepeatMode(Animation.RESTART); |
|
110 |
+ mRenderAnimator.setDuration(mDuration); |
|
111 |
+ //fuck you! the default interpolator is AccelerateDecelerateInterpolator |
|
112 |
+ mRenderAnimator.setInterpolator(new LinearInterpolator()); |
|
113 |
+ mRenderAnimator.addUpdateListener(mAnimatorUpdateListener); |
|
114 |
+ } |
|
115 |
+ |
|
116 |
+ private void invalidateSelf() { |
|
117 |
+ mCallback.invalidateDrawable(null); |
|
118 |
+ } |
|
119 |
+ |
|
120 |
+} |
@@ -0,0 +1,36 @@ |
||
1 |
+package com.android.views.loadingdrawable.render; |
|
2 |
+ |
|
3 |
+import android.content.Context; |
|
4 |
+import android.util.SparseArray; |
|
5 |
+ |
|
6 |
+import com.android.views.loadingdrawable.render.circle.rotate.GearLoadingRenderer; |
|
7 |
+ |
|
8 |
+import java.lang.reflect.Constructor; |
|
9 |
+ |
|
10 |
+public final class LoadingRendererFactory { |
|
11 |
+ private static final SparseArray<Class<? extends LoadingRenderer>> LOADING_RENDERERS = new SparseArray<>(); |
|
12 |
+ |
|
13 |
+ static { |
|
14 |
+ LOADING_RENDERERS.put(3, GearLoadingRenderer.class); |
|
15 |
+ |
|
16 |
+ } |
|
17 |
+ |
|
18 |
+ private LoadingRendererFactory() { |
|
19 |
+ } |
|
20 |
+ |
|
21 |
+ public static LoadingRenderer createLoadingRenderer(Context context, int loadingRendererId) throws Exception { |
|
22 |
+ Class<?> loadingRendererClazz = LOADING_RENDERERS.get(loadingRendererId); |
|
23 |
+ Constructor<?>[] constructors = loadingRendererClazz.getDeclaredConstructors(); |
|
24 |
+ for (Constructor<?> constructor : constructors) { |
|
25 |
+ Class<?>[] parameterTypes = constructor.getParameterTypes(); |
|
26 |
+ if (parameterTypes != null |
|
27 |
+ && parameterTypes.length == 1 |
|
28 |
+ && parameterTypes[0].equals(Context.class)) { |
|
29 |
+ constructor.setAccessible(true); |
|
30 |
+ return (LoadingRenderer) constructor.newInstance(context); |
|
31 |
+ } |
|
32 |
+ } |
|
33 |
+ |
|
34 |
+ throw new InstantiationException(); |
|
35 |
+ } |
|
36 |
+} |
@@ -0,0 +1,287 @@ |
||
1 |
+package com.android.views.loadingdrawable.render.circle.rotate; |
|
2 |
+ |
|
3 |
+import android.animation.Animator; |
|
4 |
+import android.animation.AnimatorListenerAdapter; |
|
5 |
+import android.content.Context; |
|
6 |
+import android.graphics.Canvas; |
|
7 |
+import android.graphics.Color; |
|
8 |
+import android.graphics.ColorFilter; |
|
9 |
+import android.graphics.Paint; |
|
10 |
+import android.graphics.RectF; |
|
11 |
+import android.support.annotation.IntRange; |
|
12 |
+import android.view.animation.AccelerateInterpolator; |
|
13 |
+import android.view.animation.DecelerateInterpolator; |
|
14 |
+import android.view.animation.Interpolator; |
|
15 |
+ |
|
16 |
+import com.android.views.loadingdrawable.DensityUtil; |
|
17 |
+import com.android.views.loadingdrawable.render.LoadingRenderer; |
|
18 |
+ |
|
19 |
+public class GearLoadingRenderer extends LoadingRenderer { |
|
20 |
+ private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); |
|
21 |
+ private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(); |
|
22 |
+ |
|
23 |
+ private static final int GEAR_COUNT = 4; |
|
24 |
+ private static final int NUM_POINTS = 3; |
|
25 |
+ private static final int MAX_ALPHA = 255; |
|
26 |
+ private static final int DEGREE_360 = 360; |
|
27 |
+ |
|
28 |
+ private static final int DEFAULT_GEAR_SWIPE_DEGREES = 60; |
|
29 |
+ |
|
30 |
+ private static final float FULL_GROUP_ROTATION = 3.0f * DEGREE_360; |
|
31 |
+ |
|
32 |
+ private static final float START_SCALE_DURATION_OFFSET = 0.3f; |
|
33 |
+ private static final float START_TRIM_DURATION_OFFSET = 0.5f; |
|
34 |
+ private static final float END_TRIM_DURATION_OFFSET = 0.7f; |
|
35 |
+ private static final float END_SCALE_DURATION_OFFSET = 1.0f; |
|
36 |
+ |
|
37 |
+ private static final float DEFAULT_CENTER_RADIUS = 12.5f; |
|
38 |
+ private static final float DEFAULT_STROKE_WIDTH = 2.5f; |
|
39 |
+ |
|
40 |
+ private static final int DEFAULT_COLOR = Color.WHITE; |
|
41 |
+ |
|
42 |
+ private final Paint mPaint = new Paint(); |
|
43 |
+ private final RectF mTempBounds = new RectF(); |
|
44 |
+ |
|
45 |
+ private final Animator.AnimatorListener mAnimatorListener = new AnimatorListenerAdapter() { |
|
46 |
+ @Override |
|
47 |
+ public void onAnimationRepeat(Animator animator) { |
|
48 |
+ super.onAnimationRepeat(animator); |
|
49 |
+ storeOriginals(); |
|
50 |
+ |
|
51 |
+ mStartDegrees = mEndDegrees; |
|
52 |
+ mRotationCount = (mRotationCount + 1) % NUM_POINTS; |
|
53 |
+ } |
|
54 |
+ |
|
55 |
+ @Override |
|
56 |
+ public void onAnimationStart(Animator animation) { |
|
57 |
+ super.onAnimationStart(animation); |
|
58 |
+ mRotationCount = 0; |
|
59 |
+ } |
|
60 |
+ }; |
|
61 |
+ |
|
62 |
+ private int mColor; |
|
63 |
+ |
|
64 |
+ private int mGearCount; |
|
65 |
+ private int mGearSwipeDegrees; |
|
66 |
+ |
|
67 |
+ private float mStrokeInset; |
|
68 |
+ |
|
69 |
+ private float mRotationCount; |
|
70 |
+ private float mGroupRotation; |
|
71 |
+ |
|
72 |
+ private float mScale; |
|
73 |
+ private float mEndDegrees; |
|
74 |
+ private float mStartDegrees; |
|
75 |
+ private float mSwipeDegrees; |
|
76 |
+ private float mOriginEndDegrees; |
|
77 |
+ private float mOriginStartDegrees; |
|
78 |
+ |
|
79 |
+ private float mStrokeWidth; |
|
80 |
+ private float mCenterRadius; |
|
81 |
+ |
|
82 |
+ private GearLoadingRenderer(Context context) { |
|
83 |
+ super(context); |
|
84 |
+ |
|
85 |
+ init(context); |
|
86 |
+ setupPaint(); |
|
87 |
+ addRenderListener(mAnimatorListener); |
|
88 |
+ } |
|
89 |
+ |
|
90 |
+ private void init(Context context) { |
|
91 |
+ mStrokeWidth = DensityUtil.dip2px(context, DEFAULT_STROKE_WIDTH); |
|
92 |
+ mCenterRadius = DensityUtil.dip2px(context, DEFAULT_CENTER_RADIUS); |
|
93 |
+ |
|
94 |
+ mColor = DEFAULT_COLOR; |
|
95 |
+ |
|
96 |
+ mGearCount = GEAR_COUNT; |
|
97 |
+ mGearSwipeDegrees = DEFAULT_GEAR_SWIPE_DEGREES; |
|
98 |
+ } |
|
99 |
+ |
|
100 |
+ private void setupPaint() { |
|
101 |
+ mPaint.setAntiAlias(true); |
|
102 |
+ mPaint.setStrokeWidth(mStrokeWidth); |
|
103 |
+ mPaint.setStyle(Paint.Style.STROKE); |
|
104 |
+ mPaint.setStrokeCap(Paint.Cap.ROUND); |
|
105 |
+ |
|
106 |
+ initStrokeInset(mWidth, mHeight); |
|
107 |
+ } |
|
108 |
+ |
|
109 |
+ @Override |
|
110 |
+ protected void draw(Canvas canvas) { |
|
111 |
+ int saveCount = canvas.save(); |
|
112 |
+ |
|
113 |
+ mTempBounds.set(mBounds); |
|
114 |
+ mTempBounds.inset(mStrokeInset, mStrokeInset); |
|
115 |
+ mTempBounds.inset(mTempBounds.width() * (1.0f - mScale) / 2.0f, mTempBounds.width() * (1.0f - mScale) / 2.0f); |
|
116 |
+ |
|
117 |
+ canvas.rotate(mGroupRotation, mTempBounds.centerX(), mTempBounds.centerY()); |
|
118 |
+ |
|
119 |
+ mPaint.setColor(mColor); |
|
120 |
+ mPaint.setAlpha((int) (MAX_ALPHA * mScale)); |
|
121 |
+ mPaint.setStrokeWidth(mStrokeWidth * mScale); |
|
122 |
+ |
|
123 |
+ if (mSwipeDegrees != 0) { |
|
124 |
+ for (int i = 0; i < mGearCount; i++) { |
|
125 |
+ canvas.drawArc(mTempBounds, mStartDegrees + DEGREE_360 / mGearCount * i, mSwipeDegrees, false, mPaint); |
|
126 |
+ } |
|
127 |
+ } |
|
128 |
+ |
|
129 |
+ canvas.restoreToCount(saveCount); |
|
130 |
+ } |
|
131 |
+ |
|
132 |
+ @Override |
|
133 |
+ protected void computeRender(float renderProgress) { |
|
134 |
+ // Scaling up the start size only occurs in the first 20% of a single ring animation |
|
135 |
+ if (renderProgress <= START_SCALE_DURATION_OFFSET) { |
|
136 |
+ float startScaleProgress = (renderProgress) / START_SCALE_DURATION_OFFSET; |
|
137 |
+ mScale = DECELERATE_INTERPOLATOR.getInterpolation(startScaleProgress); |
|
138 |
+ } |
|
139 |
+ |
|
140 |
+ // Moving the start trim only occurs between 20% to 50% of a single ring animation |
|
141 |
+ if (renderProgress <= START_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) { |
|
142 |
+ float startTrimProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (START_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET); |
|
143 |
+ mStartDegrees = mOriginStartDegrees + mGearSwipeDegrees * startTrimProgress; |
|
144 |
+ } |
|
145 |
+ |
|
146 |
+ // Moving the end trim starts between 50% to 80% of a single ring animation |
|
147 |
+ if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_TRIM_DURATION_OFFSET) { |
|
148 |
+ float endTrimProgress = (renderProgress - START_TRIM_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_TRIM_DURATION_OFFSET); |
|
149 |
+ mEndDegrees = mOriginEndDegrees + mGearSwipeDegrees * endTrimProgress; |
|
150 |
+ } |
|
151 |
+ |
|
152 |
+ // Scaling down the end size starts after 80% of a single ring animation |
|
153 |
+ if (renderProgress > END_TRIM_DURATION_OFFSET) { |
|
154 |
+ float endScaleProgress = (renderProgress - END_TRIM_DURATION_OFFSET) / (END_SCALE_DURATION_OFFSET - END_TRIM_DURATION_OFFSET); |
|
155 |
+ mScale = 1.0f - ACCELERATE_INTERPOLATOR.getInterpolation(endScaleProgress); |
|
156 |
+ } |
|
157 |
+ |
|
158 |
+ if (renderProgress <= END_TRIM_DURATION_OFFSET && renderProgress > START_SCALE_DURATION_OFFSET) { |
|
159 |
+ float rotateProgress = (renderProgress - START_SCALE_DURATION_OFFSET) / (END_TRIM_DURATION_OFFSET - START_SCALE_DURATION_OFFSET); |
|
160 |
+ mGroupRotation = ((FULL_GROUP_ROTATION / NUM_POINTS) * rotateProgress) + (FULL_GROUP_ROTATION * (mRotationCount / NUM_POINTS)); |
|
161 |
+ } |
|
162 |
+ |
|
163 |
+ if (Math.abs(mEndDegrees - mStartDegrees) > 0) { |
|
164 |
+ mSwipeDegrees = mEndDegrees - mStartDegrees; |
|
165 |
+ } |
|
166 |
+ } |
|
167 |
+ |
|
168 |
+ @Override |
|
169 |
+ protected void setAlpha(int alpha) { |
|
170 |
+ mPaint.setAlpha(alpha); |
|
171 |
+ } |
|
172 |
+ |
|
173 |
+ @Override |
|
174 |
+ protected void setColorFilter(ColorFilter cf) { |
|
175 |
+ mPaint.setColorFilter(cf); |
|
176 |
+ } |
|
177 |
+ |
|
178 |
+ @Override |
|
179 |
+ protected void reset() { |
|
180 |
+ resetOriginals(); |
|
181 |
+ } |
|
182 |
+ |
|
183 |
+ private void initStrokeInset(float width, float height) { |
|
184 |
+ float minSize = Math.min(width, height); |
|
185 |
+ float strokeInset = minSize / 2.0f - mCenterRadius; |
|
186 |
+ float minStrokeInset = (float) Math.ceil(mStrokeWidth / 2.0f); |
|
187 |
+ mStrokeInset = strokeInset < minStrokeInset ? minStrokeInset : strokeInset; |
|
188 |
+ } |
|
189 |
+ |
|
190 |
+ private void storeOriginals() { |
|
191 |
+ mOriginEndDegrees = mEndDegrees; |
|
192 |
+ mOriginStartDegrees = mEndDegrees; |
|
193 |
+ } |
|
194 |
+ |
|
195 |
+ private void resetOriginals() { |
|
196 |
+ mOriginEndDegrees = 0; |
|
197 |
+ mOriginStartDegrees = 0; |
|
198 |
+ |
|
199 |
+ mEndDegrees = 0; |
|
200 |
+ mStartDegrees = 0; |
|
201 |
+ |
|
202 |
+ mSwipeDegrees = 1; |
|
203 |
+ } |
|
204 |
+ |
|
205 |
+ private void apply(Builder builder) { |
|
206 |
+ this.mWidth = builder.mWidth > 0 ? builder.mWidth : this.mWidth; |
|
207 |
+ this.mHeight = builder.mHeight > 0 ? builder.mHeight : this.mHeight; |
|
208 |
+ this.mStrokeWidth = builder.mStrokeWidth > 0 ? builder.mStrokeWidth : this.mStrokeWidth; |
|
209 |
+ this.mCenterRadius = builder.mCenterRadius > 0 ? builder.mCenterRadius : this.mCenterRadius; |
|
210 |
+ |
|
211 |
+ this.mDuration = builder.mDuration > 0 ? builder.mDuration : this.mDuration; |
|
212 |
+ |
|
213 |
+ this.mColor = builder.mColor != 0 ? builder.mColor : this.mColor; |
|
214 |
+ |
|
215 |
+ this.mGearCount = builder.mGearCount > 0 ? builder.mGearCount : this.mGearCount; |
|
216 |
+ this.mGearSwipeDegrees = builder.mGearSwipeDegrees > 0 ? builder.mGearSwipeDegrees : this.mGearSwipeDegrees; |
|
217 |
+ |
|
218 |
+ setupPaint(); |
|
219 |
+ initStrokeInset(this.mWidth, this.mHeight); |
|
220 |
+ } |
|
221 |
+ |
|
222 |
+ public static class Builder { |
|
223 |
+ private Context mContext; |
|
224 |
+ |
|
225 |
+ private int mWidth; |
|
226 |
+ private int mHeight; |
|
227 |
+ private int mStrokeWidth; |
|
228 |
+ private int mCenterRadius; |
|
229 |
+ |
|
230 |
+ private int mDuration; |
|
231 |
+ |
|
232 |
+ private int mColor; |
|
233 |
+ |
|
234 |
+ private int mGearCount; |
|
235 |
+ private int mGearSwipeDegrees; |
|
236 |
+ |
|
237 |
+ public Builder(Context mContext) { |
|
238 |
+ this.mContext = mContext; |
|
239 |
+ } |
|
240 |
+ |
|
241 |
+ public Builder setWidth(int width) { |
|
242 |
+ this.mWidth = width; |
|
243 |
+ return this; |
|
244 |
+ } |
|
245 |
+ |
|
246 |
+ public Builder setHeight(int height) { |
|
247 |
+ this.mHeight = height; |
|
248 |
+ return this; |
|
249 |
+ } |
|
250 |
+ |
|
251 |
+ public Builder setStrokeWidth(int strokeWidth) { |
|
252 |
+ this.mStrokeWidth = strokeWidth; |
|
253 |
+ return this; |
|
254 |
+ } |
|
255 |
+ |
|
256 |
+ public Builder setCenterRadius(int centerRadius) { |
|
257 |
+ this.mCenterRadius = centerRadius; |
|
258 |
+ return this; |
|
259 |
+ } |
|
260 |
+ |
|
261 |
+ public Builder setDuration(int duration) { |
|
262 |
+ this.mDuration = duration; |
|
263 |
+ return this; |
|
264 |
+ } |
|
265 |
+ |
|
266 |
+ public Builder setColor(int color) { |
|
267 |
+ this.mColor = color; |
|
268 |
+ return this; |
|
269 |
+ } |
|
270 |
+ |
|
271 |
+ public Builder setGearCount(int gearCount) { |
|
272 |
+ this.mGearCount = gearCount; |
|
273 |
+ return this; |
|
274 |
+ } |
|
275 |
+ |
|
276 |
+ public Builder setGearSwipeDegrees(@IntRange(from = 0, to = 360) int gearSwipeDegrees) { |
|
277 |
+ this.mGearSwipeDegrees = gearSwipeDegrees; |
|
278 |
+ return this; |
|
279 |
+ } |
|
280 |
+ |
|
281 |
+ public GearLoadingRenderer build() { |
|
282 |
+ GearLoadingRenderer loadingRenderer = new GearLoadingRenderer(mContext); |
|
283 |
+ loadingRenderer.apply(this); |
|
284 |
+ return loadingRenderer; |
|
285 |
+ } |
|
286 |
+ } |
|
287 |
+} |
@@ -121,4 +121,31 @@ |
||
121 | 121 |
<attr name="hasStickyHeaders" format="boolean" /> |
122 | 122 |
<attr name="isDrawingListUnderStickyHeader" format="boolean" /> |
123 | 123 |
</declare-styleable> |
124 |
+ |
|
125 |
+ <declare-styleable name="LoadingView"> |
|
126 |
+ <attr name="loading_renderer"> |
|
127 |
+ <!--circle rotate--> |
|
128 |
+ <enum name="MaterialLoadingRenderer" value="0"/> |
|
129 |
+ <enum name="LevelLoadingRenderer" value="1"/> |
|
130 |
+ <enum name="WhorlLoadingRenderer" value="2"/> |
|
131 |
+ <enum name="GearLoadingRenderer" value="3"/> |
|
132 |
+ <!--circle jump--> |
|
133 |
+ <enum name="SwapLoadingRenderer" value="4"/> |
|
134 |
+ <enum name="GuardLoadingRenderer" value="5"/> |
|
135 |
+ <enum name="DanceLoadingRenderer" value="6"/> |
|
136 |
+ <enum name="CollisionLoadingRenderer" value="7"/> |
|
137 |
+ <!--Scenery--> |
|
138 |
+ <enum name="DayNightLoadingRenderer" value="8"/> |
|
139 |
+ <enum name="ElectricFanLoadingRenderer" value="9"/> |
|
140 |
+ <!--Animal--> |
|
141 |
+ <enum name="FishLoadingRenderer" value="10"/> |
|
142 |
+ <enum name="GhostsEyeLoadingRenderer" value="11"/> |
|
143 |
+ <!--Goods--> |
|
144 |
+ <enum name="BalloonLoadingRenderer" value="12"/> |
|
145 |
+ <enum name="WaterBottleLoadingRenderer" value="13"/> |
|
146 |
+ <!--ShapeChange--> |
|
147 |
+ <enum name="CircleBroodLoadingRenderer" value="14"/> |
|
148 |
+ <enum name="CoolWaitLoadingRenderer" value="15"/> |
|
149 |
+ </attr> |
|
150 |
+ </declare-styleable> |
|
124 | 151 |
</resources> |